package org.common.eureka.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.common.eureka.config.TopicRabbitConfig;
import org.common.eureka.mapper.BusiOrderMapper;
import org.common.eureka.service.IOrderService;
import org.sam.commons.entity.dto.BusiOrderDto;
import org.sam.commons.entity.dto.BusiOrderQueryDto;
import org.sam.commons.entity.dto.BusiSubmitOrdersDto;
import org.sam.commons.entity.enums.PayTypeEnum;
import org.sam.commons.entity.pojo.BusiOrder;
import org.sam.commons.exception.CustomException;
import org.sam.commons.util.OrderNoWorker;
import org.sam.commons.util.SnowflakeIdWorker;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 缓存穿透处理办法
 *
 * @author samphin
 * @since 2020-6-14 10:19:17
 */
@Service
public class OrderServiceImpl implements IOrderService {

    @Autowired
    private BusiOrderMapper orderMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 定义线程安全的锁集合
     */
    private ConcurrentHashMap<String, Lock> locks = new ConcurrentHashMap<>();

    // 声时一个布隆过滤器
    private BloomFilter<CharSequence> bf = null;

    // 在bean初始化完成后，实例化bloomFilter，并加载数据
    @PostConstruct
    public void init() {
        List<BusiOrder> orders = orderMapper.selectAll();
        bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), orders.size());
        for (BusiOrder order : orders) {
            bf.put(String.valueOf(order.getId()));
        }
    }

    @CachePut(value = "order")
    @Override
    public BusiOrder save(BusiOrderDto orderDto) {
        BusiOrder order = new BusiOrder().buildPo(orderDto);
        order.setId(SnowflakeIdWorker.generateId());
        order.setOrderNo(OrderNoWorker.generateOrderNo(10002));
        order.setCreateDate(new Date());
        int count = this.orderMapper.insertSelective(order);
        return order;
    }

    /**
     * 秒杀商品
     */
    @Override
    public void secKill(BusiSubmitOrdersDto ordersDto, Long userId) {
        //商品ID
        long goodsId = ordersDto.getGoodsId();
        try {
            //加锁排队，阻塞式锁
            doLock(String.valueOf(goodsId));//1000个线程
            //一个人只能抢购一件，不允许重复抢
            Boolean isExist = redisTemplate.hasKey("orders");
            HashOperations hashOperations = redisTemplate.opsForHash();
            if (isExist) {
                Boolean goods = hashOperations.hasKey("orders", userId.toString() + ":" + goodsId);
                if (goods) {
                    throw new CustomException("亲！单个商品只能购买一次哦！");
                }else{
                    Map<String, Object> order = new HashMap<>();
                    order.put(userId.toString() + ":" + goodsId, userId);
                    hashOperations.putAll("orders", order);
                }
            }

            //提交订单数量超过库存量，也必须保证库存稳定减少，不能减成负数

            //转换订单信息
            BusiOrder po = new BusiOrder().buildPo(ordersDto);

            //设置订单ID
            long orderId = SnowflakeIdWorker.generateId();
            //设置订单ID
            po.setId(orderId);
            //设置订单号
            po.setOrderNo(OrderNoWorker.generateOrderNo(userId.intValue()));
            po.setCreateDate(new Date());
            po.setCreateUserId(userId.intValue());
            po.setPayCode(PayTypeEnum.WE_CHAT.getCode());
            po.setPayName(PayTypeEnum.WE_CHAT.getName());

            //减对应商品库存量
            //获取库存量
            int newestDepotCount = (int) hashOperations.get("goods", "depot:" + goodsId);
            System.out.println("库存量还剩："+newestDepotCount);
            if (newestDepotCount == 0) {
                return;
            }
            hashOperations.increment("goods", "depot:" + goodsId, -1);

            String orderInfo = JSONObject.toJSONString(po);
            //如果采用延时队列，订单30分钟未支付，自动取消订单
            rabbitTemplate.convertAndSend(
                    TopicRabbitConfig.ORDER_INFO_EXCHANGE,
                    TopicRabbitConfig.ORDER_INFO_ROUTE_KEY,
                    orderInfo);

        } catch (Exception e) {
            throw new CustomException("提交订单失败", e);
        } finally {
            //解锁
            releaseLock(String.valueOf(goodsId));
        }
    }

    @CacheEvict(value = "order", key = "#orderId")
    @Override
    public boolean delete(Long id) {
        int count = this.orderMapper.deleteByPrimaryKey(id);
        return count > 0 ? true : false;
    }

    /**
     * 查询单个对象
     *
     * @param id
     * @author samphin
     */
    @Override
    @Cacheable(value = "order")
    public BusiOrder queryById(Long id) {
        // 先判断布隆过滤器中是否存在该值，值存在才允许访问缓存和数据库
        if (!bf.mightContain(String.valueOf(id))) {
            System.out.println("非法访问-----------" + System.currentTimeMillis());
            return null;
        }

        System.out.println("数据库中得到数据-----------" + System.currentTimeMillis());
        BusiOrder busiOrder = this.orderMapper.selectByPrimaryKey(id);
        return busiOrder;
    }


    @Override
    @Cacheable(value = "orders")
    public List<BusiOrder> queryList(BusiOrderQueryDto queryDto) {

        Example example = new Example(BusiOrder.class);
        example.createCriteria().andLike("orderNo", queryDto.getOrderNo());

        List<BusiOrder> busiOrders = this.orderMapper.selectByExample(example);

        return busiOrders;
    }

    /**
     * 释放锁
     *
     * @param lockCode
     */
    private void releaseLock(String lockCode) {
        ReentrantLock oldLock = (ReentrantLock) locks.get(lockCode);
        if (oldLock != null && oldLock.isHeldByCurrentThread()) {
            oldLock.unlock();
        }
    }


    /**
     * 给指定商品进行加锁
     *
     * @param lockCode
     */
    private void doLock(String lockCode) {

        //lockcode 有不同的值，参数多样化
        //lockcode 相同的，加一个锁，---- 不是同一个key，不能用同一个锁
        //创建一个锁
        ReentrantLock newLock = new ReentrantLock();
        //若已存在，则newLock直接丢弃
        Lock oldLock = locks.putIfAbsent(lockCode, newLock);
        if (oldLock == null) {
            newLock.lock();
        } else {
            oldLock.lock();
        }
    }
}
